package game.socket;

import dou.net.protocol.ProtocolManager;
import game.services.MsgID;
import game.services.session.SessionProtocol;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import protocol.Session;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 管理全部连接到服务端的客户端套接字
 * @author wizardc
 */
public class ClientSocketManager
{
    private static Logger logger = LogManager.getLogger("GameServer");

    // 记录该类的唯一实例
    private static ClientSocketManager _instance;

    /**
     * 获取该类全局唯一的实例
     * @return 该类全局唯一的实例
     */
    public static synchronized ClientSocketManager getInstance()
    {
        if(_instance == null)
        {
            _instance = new ClientSocketManager();
        }
        return _instance;
    }

    // 记录程序重入锁对象
    private final ReentrantLock _lock = new ReentrantLock();

    // 记录所有连接上客户端的哈希表
    private ConcurrentHashMap<Integer, RemoteClientSocket> _clientMap;
    // 记录当前在线用户的数量
    private AtomicInteger _onlineUserCount;

    /**
     * 创建一个 ClientSocketManager 对象
     */
    private ClientSocketManager()
    {
        // 创建记录客户端的哈希表，ConcurrentHashMap 表是线程安全的表
        _clientMap = new ConcurrentHashMap<Integer, RemoteClientSocket>();
        // 创建原子对象，该对象线程安全
        _onlineUserCount = new AtomicInteger(0);
    }

    /**
     * 添加一个新的客户端
     * @param uid 客户端的用户 ID
     * @param socket 对应客户端的 socket 对象
     * @return 该客户端的信息确认是否成功
     */
    public boolean addClient(int uid, RemoteClientSocket socket)
    {
        if(!checkUserLogin(uid, socket.ipAddress()))
        {
            closeClient(socket);
            return false;
        }
        // 使用同步锁保证线程安全
        _lock.lock();
        try
        {
            // 如果之前已经登录则踢掉之前的登录
            RemoteClientSocket oldClient = _clientMap.get(uid);
            if(oldClient != null)
            {
                closeClient(oldClient);
            }
            // 添加客户端用户
            _clientMap.put(uid, socket);
            // 设置该套接字客户端的用户信息
            socket.uid(uid);
            // 用户上线处理方法
            loginHandler(socket);
        }
        finally
        {
            _lock.unlock();
        }
        // 增加在线用户数
        _onlineUserCount.getAndIncrement();
        return true;
    }

    /**
     * 用户登录后的处理方法
     * @param socket 登录的客户端套接字对象
     */
    void loginHandler(RemoteClientSocket socket)
    {
        // 启动心跳
        socket.startKeepAlive();
    }

    /**
     * 检测用户登录信息是否正确
     * @param uid 用户 ID
     * @param ipLong 用户的整形 IP 地址
     * @return 数据是否匹配，正确返回 true，否则返回 false
     */
    private boolean checkUserLogin(int uid, long ipLong)
    {
        // TODO : 这里一般进行数据库的查询
        return true;
    }

    /**
     * 关闭一个客户端
     * @param socket 对应客户端的 socket 对象
     */
    private void closeClient(RemoteClientSocket socket)
    {
        try
        {
            socket.close();
        }
        catch(IOException exception)
        {
            logger.error(exception);
        }
    }

    /**
     * 将一个客户端从该对象中移除
     * @param uid 要移除的用户 ID
     */
    public void removeClient(int uid)
    {
        _lock.lock();
        try
        {
            _clientMap.remove(uid);
        }
        finally
        {
            _lock.unlock();
        }
        // 减少一个在线用户数
        _onlineUserCount.getAndDecrement();
    }

    /**
     * 用户注销后的处理方法
     * 当调用到该方法后表示客户端下一步需要进行断开连接的处理，仅当客户端发送了登录消息后才会调用该方法
     * 一般会调用到该方法的情况：
     * 1. 客户端主动断开连接, 但必须发送过登录消息记录了 uid 时才会调用
     * 2. 客户端 uid 登录时踢掉已登录的相同 uid
     * 3. 心跳没有返回, 断网时服务端主动踢掉客户端
     * @param socket 注销的客户端套接字对象
     */
    void logoutHandler(RemoteClientSocket socket)
    {
        try
        {
            // 停止心跳
            socket.stopKeepAlive();
            // 发送登出消息
            SessionProtocol sessionProtocol = (SessionProtocol) ProtocolManager.getInstance().getProtocol(MsgID.Req_SessionLogout.value());
            Session.Req_SessionLogout.Builder builder = Session.Req_SessionLogout.newBuilder();
            builder.setUid(socket.uid());
            Session.Req_SessionLogout message = builder.build();
            sessionProtocol.messageHandler(MsgID.Req_SessionLogout.value(), 0, message, socket);
        }
        catch(Exception exception)
        {
            logger.error("user: " + socket.uid() + " logout failure!", exception);
        }
    }

    /**
     * 获取所有在线客户端表
     * @return 所有在线客户端表
     */
    public ConcurrentHashMap<Integer, RemoteClientSocket> getClientMap()
    {
        return _clientMap;
    }

    /**
     * 获取一个指定客户端的 IP 地址
     * @param uid 指定的用户 ID
     * @return 该客户端的 IP 地址
     */
    public String getClientIP(int uid)
    {
        RemoteClientSocket client = _clientMap.get(uid);
        if(client != null)
        {
            return client.ipAddressStr();
        }
        else
        {
            logger.error("user: " + uid + " not online!");
            return null;
        }
    }

    /**
     * 获取一个指定的用户是否在线
     * @param uid 指定的用户 ID
     * @return 如果该用户在线返回 true，否则返回 false
     */
    public boolean isOnline(int uid)
    {
        return _clientMap.containsKey(uid);
    }

    /**
     * 获取当前的在线总数
     * @return 当前的在线总数
     */
    public int getOnlineUserCount()
    {
        return _onlineUserCount.get();
    }

    /**
     * 清空该管理对象并释放对应的资源
     */
    public void cleanup()
    {
        if(_clientMap != null && _clientMap.size() > 0)
        {
            for(RemoteClientSocket socket : _clientMap.values())
            {
                closeClient(socket);
            }
            _clientMap.clear();
        }
    }
}
